import logging
import logging.config
from os import path, abort, makedirs
import signal
from yaml import safe_load
from time import sleep
import paho.mqtt.client as mqttclient
from simplejson import JSONDecodeError, loads
import sqlite3
import json
import random


class JujiSimHost:
    def __init__(self) -> None:
        signal.signal(signal.SIGINT, lambda _, __: self.stop())
        self.stopped = False
        self.devicelist = []
        self.filePath = path.dirname(path.dirname(path.abspath(__file__)))
        self.creatLog()

        # 连接数据库，建立游标
        self.conn = sqlite3.connect("data.db")
        # 赋值给 self.cur 后，可以随时用 self.cur.execute(...) 执行 SQL
        self.cur = self.conn.cursor()
        # 如果没有数据库，则创建数据库
        if not self.check("data.db", "last_data"):
            sql_test_1 = """CREATE TABLE last_data(name TEXT, label TEXT, ts TEXT);"""
            self.cur.execute(sql_test_1)
        yamlPath = (self.filePath +
                    '/config/sim_host.yaml').replace('/', path.sep)
        with open(yamlPath, "r") as yaml:
            self.config = safe_load(yaml)
        self.mqttConfg = self.config["mqtt"]
        for item in self.config["devices"]:
            typeFile = self.filePath + \
                f"/{item['type']}.json".replace("/", path.sep)
            with open(typeFile, "r") as file:
                typeJson = json.load(file)
            item["topic"] = typeJson["topic"]
            item["ts"] = typeJson["ts"]
            self.devicelist.append(item)

        self.client = mqttclient.Client(protocol=mqttclient.MQTTv311)
        self.client.on_connect = lambda _, __, ____, rc: self.on_connect_cb(rc)
        self.client.on_message = self._on_message
        self.client.on_subscribe = self._on_subscribe

    def creatLog(self):
        log_dir = path.join(self.filePath, "logs")
        makedirs(log_dir, exist_ok=True)
        logging.config.fileConfig(
            self.filePath + "/config/logs.conf".replace('/', path.sep), disable_existing_loggers=False)
        global _log
        _log = logging.getLogger('main')

    def on_connect_cb(self, rc: int):
        if rc == 0:
            _log.info("连接成功")
        else:
            _log.error(f"mqtt连接失败:{rc}")

    def _on_message(self, client, userdata, message):
        _log.debug(
            f"发布端收到消息topic:{message.topic}  内容:{message.payload.decode('utf-8')}")

    def _on_subscribe(self, _, __, mid, granted_qos, *args):
        _log.info("订阅成功")

    # 发送数据
    def sendMsg(self, device):
        topic = device["topic"] + device["name"]
        valueDic = {}
        for item in device["ts"]:
            if item["type"] == "add":
                randomValue = random.randint(1, int(item["range_p"]))
                value = float(item["value"]) + float(randomValue)
                if item["max"] is not None:
                    if value > float(item["max"]):
                        value = float(item["value"])
                item["value"] = value
            if item["type"] == "random":
                value = float(item["value"]) * \
                    (1 - (random.randint(1, int(item["range_p"])) / 100))
            valueDic[item["key"]] = value
        self.client.publish(topic, json.dumps(valueDic))
        self.save_lastdata(
            device["name"], device["label"], json.dumps(valueDic))
        _log.info(f"发送数据:topic:{topic},内容:{json.dumps(valueDic)}")

    def start(self) -> bool:
        _log.info("程序启动...")
        if self.mqttConfg["user"] is not None:
            self.client.username_pw_set(
                self.mqttConfg["user"], self.mqttConfg["password"])
        self.client.connect_async(
            self.mqttConfg["host"], self.mqttConfg["port"])
        self.client.loop_start()
        interval = self.config["interval"]
        self.load_data()

        while not self.stopped:
            for device in self.devicelist:
                self.sendMsg(device)
            sleep(interval)

    def stop(self) -> None:
        self.cur.close()
        self.conn.close()
        self.client.loop_stop()
        self.stopped = True
        _log.info("程序停止...")
        abort()

    @staticmethod
    def decode(message):
        try:
            if isinstance(message.payload, bytes):
                content = loads(message.payload.decode("utf-8", "ignore"))
            else:
                content = loads(message.payload)
        except JSONDecodeError:
            try:
                content = message.payload.decode("utf-8", "ignore")
            except JSONDecodeError:
                content = message.payload
        return content

    # 检查 SQLite 数据库里是否存在指定的表
    @staticmethod
    def check(db_name, table_name) -> bool:
        with sqlite3.connect(db_name) as conn:  # 自动管理连接关闭
            cursor = conn.cursor()
            cursor.execute(
                "SELECT 1 FROM sqlite_master WHERE type='table' AND tbl_name=? LIMIT 1",
                (table_name,)
            )
            return cursor.fetchone() is not None

    # 保存最新数据
    def save_lastdata(self, name, label, data):
        SELECT = "SELECT * FROM last_data WHERE name = ?;"
        UPDATE = "UPDATE last_data SET ts=? WHERE name=?;"
        INSERT = "INSERT INTO last_data (name, label, ts) VALUES (?, ?, ?);"
        if not self.cur.execute(SELECT, (name,)).fetchall():
            self.cur.execute(INSERT, (name, label, data))
        else:
            self.cur.execute(UPDATE, (data, name))
        self.conn.commit()

    # 从数据库读取数据
    def load_data(self):
        isexist = self.cur.execute(
            "SELECT name FROM sqlite_master WHERE type='table' AND name='last_data'")
        fetchall = isexist.fetchall()
        if not fetchall:
            _log.error("数据库中没有找到last_data表")
        else:
            for device in self.devicelist:
                isexistts = self.cur.execute(
                    f"SELECT ts FROM last_data WHERE name='{device['name']}'")
                fetchallts = isexistts.fetchone()
                if fetchallts is not None:
                    ts_dict = json.loads(fetchallts[0])
                    for tsItem in device["ts"]:
                        if tsItem.get("type") == "add" and tsItem["key"] in ts_dict:
                            tsItem["value"] = ts_dict[tsItem["key"]]


if __name__ == '__main__':
    host = JujiSimHost()
    host.start()
